package com.base.monitor.module.biz.message.impl;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.StrUtil;
import com.base.monitor.framework.exception.CustomerException;
import com.base.monitor.framework.util.CustomerUtils;
import com.base.monitor.model.constant.CollectConstant;
import com.base.monitor.model.enums.biz.CollectTypeEnum;
import com.base.monitor.model.enums.biz.TandaFireControlMainEngineCodeEnum;
import com.base.monitor.model.enums.code.impl.common.ParamCode;
import com.base.monitor.model.enums.code.impl.common.ResultCode;
import com.base.monitor.model.model.biz.cfg.entity.Gateway;
import com.base.monitor.model.model.biz.cfg.vo.IndicatorCommandVo;
import com.base.monitor.model.model.biz.collect.entity.CollectData;
import com.base.monitor.module.biz.context.CfgContext;
import com.base.monitor.module.biz.context.ClientContext;
import com.base.monitor.module.biz.handler.HandleCollectDataHandler;
import com.base.monitor.module.biz.handler.PushFireControlUpdateTimeHandler;
import com.base.monitor.module.biz.message.Message;
import com.base.monitor.module.biz.util.AsciiUtils;
import com.base.monitor.module.biz.util.ChannelUtils;
import com.base.monitor.module.biz.util.ConvertUtils;
import io.netty.channel.ChannelHandlerContext;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;

/**
 * 泰达安消防主机的消息
 */
@Component
@Slf4j
public class TandaFireControlMessage implements Message {

    @Autowired
    private HandleCollectDataHandler handleCollectDataHandler;

    /**
     * 消息类型
     *
     * @return
     */
    @Override
    public String messageType() {
        return CollectConstant.MESSAGE_TYPE_IS_TANDA_FIRE_CONTROL;
    }

    /**
     * 处理消息
     *
     * @param ctx     netty客户端通道信息
     * @param message 消息内容
     */
    @Override
    public void handle(ChannelHandlerContext ctx, String message) {
//        message = "404028840201000000000000010000000000000000000000CA00020204010188313035303231333150BFB5D1F8354600000000000000000000000000000000000000000000000000000330081605150101BE39393939393030343C0000000000000000000000000000000000000000000000000000000000000000252D1516061501017F313231303331353280B1C3B7BF00000000000000000000000000000000000000000000000000000000131F06190615010186313231303331353380B1C3B7BF000000000000000000000000000000000000000000000000000000000006001A0615F52323";
//        message = "4040298402010000000000000100000000000000000000000200020900BE2323404029840201000000000000010000000000000000000000000003B42323";
        log.info("[泰达安消防]处理ip为{}的消防主机消息消息，消息内容为{}", ctx.channel().remoteAddress(), message);

        // 1.查找是否已经连接过，没有连接过要先注册
        connectedIfAbsent(ctx);

        // 2.查找网关信息
        Gateway gateway = getGatewayByIp(ChannelUtils.formatChannelIp(ctx.channel().remoteAddress().toString()));

        // 3.解析消息
        List<CollectData> list = parseFireControlMessage(ctx, message, gateway.getGatewayId());

        // 4.更新网关和指标的在线状态
        updateOnlineTime(gateway);

        // 5.推送给远端
        pushCollect(list);
    }

    /**
     * 如果该信道不存在的话就要注册信道
     * @param ctx 信道上下文
     */
    private void connectedIfAbsent(ChannelHandlerContext ctx) {
        // 1.获取ip地址
        String ip = ChannelUtils.formatChannelIp(ctx.channel().remoteAddress().toString());

        ClientContext.ChannelInfo channelInfo = ClientContext.getChannelInfoByIp(ip);
        if (channelInfo == null) {
            // 2.生成连接命令。因为消防主机是没有连接命令的
            String connect = manualGenerateConnectMessage();

            // 3.注册信道
            ClientContext.addClient(connect, ctx);
        }
    }

    /**
     * 解析消防主机的消息（一条会有多个设备的信息）
     * @param ctx 信道上下文
     * @param message 消防主机的消息
     * @param gatewayId 网关id
     * @return
     */
    private List<CollectData> parseFireControlMessage(ChannelHandlerContext ctx, String message, Long gatewayId) {
        String type = message.substring(52, 56);
        if (type.equals("0209")) {
            log.info("[泰达安消防]该消防消息为心跳消息");
            PushFireControlUpdateTimeHandler.addFireControlOnlineTimeMap(gatewayId);
            return null;
        }
        if (type.equals("0204")) {
            log.info("[泰达安消防]该消防消息为复位消息");
            List<String> list = new ArrayList<>();
            list.add(gatewayId + "");
            list.add("RESET");
            return Collections.singletonList(new CollectData("", CollectTypeEnum.FIRE, list));
        }
        if (type.equals("0202")) {
            log.info("[泰达安消防]该消防消息为报警消息");
            return this.doParseFireControlMessage(ctx, message);
        }
        return null;
    }

    /**
     * 更新网关和指标的在线时间
     * @param gateway 网关
     */
    private void updateOnlineTime(Gateway gateway) {
        // 1.更新网关在线时间
        CfgContext.addGatewayOnlineTime(gateway.getGatewayId());

        // 2.更新指标在线时间
        List<IndicatorCommandVo> indicators = gateway.getIndicators();
        if (CollectionUtil.isEmpty(indicators)) {
            return;
        }
        List<String> ids = indicators.stream().map(IndicatorCommandVo::getId).collect(Collectors.toList());
        if (CollectionUtil.isEmpty(indicators)) {
            return;
        }
        for (String id : ids) {
            CfgContext.addIndicatorOnlineTime(id);
        }
    }

    /**
     * 推送给远端
     */
    private void pushCollect(List<CollectData> list) {
        // CRT相当于心跳，数据不会发送出去
        if (CollectionUtil.isEmpty(list)) {
            log.info("[泰达安消防]该消防消息:{}为心跳消息，不需要推送到远端", list);
            return;
        }
        log.warn("[泰达安消防]该消防消息:{}为报警消息，需要推送到远端", list);
        for (CollectData collect : list) {
            handleCollectDataHandler.addTask(collect);
        }
    }

    /**
     * 手动生成连接命令
     * @return
     */
    private String manualGenerateConnectMessage() {
        String message = generateConnectMessage();
        if (ClientContext.getClientMap().containsKey(message)) {
            message = manualGenerateConnectMessage();
        }
        return message;
    }

    /**
     * 生成随机的连接命令
     * @return
     */
    private String generateConnectMessage() {
        return "[泰达安消防]" + CustomerUtils.getRandomString(5);
    }

    /**
     * 真正解析消息
     * @param ctx 信道
     * @param message 消息
     */
    private List<CollectData> doParseFireControlMessage(ChannelHandlerContext ctx, String message) {
        // 获取本条消息中涉及多少个设备
        String numHex = message.substring(56, 58);
        int number = ConvertUtils.hexToDec(numHex);

        // 分割消息并处理
        List<CollectData> list = new ArrayList<>();
        for (int i = 0 ; i < number ; i++) {
            list.add(parseSingletonFireControlMessage(ctx, message.substring(58 + i * 100, 158 + i * 100)));
        }
        return list;
    }

    /**
     * 解析单条消息
     * @param ctx 信道
     * @param message 消息
     * @return
     */
    private CollectData parseSingletonFireControlMessage(ChannelHandlerContext ctx, String message) {
        // 获取设备编号，即指标编号
        String indicatorAscii = message.substring(6, 22);

        // 获取报警类型
        String alarmTypeHex = message.substring(22, 24);

        // 解析编号
        String indicatorId = parseIndicatorAscii(indicatorAscii);

        // 解析报警类型
        String alarmType = parseAlarmTypeHex(alarmTypeHex);

        // 组装并返回采集对象
        return assembleAndReturn(indicatorId, alarmType);
    }

    /**
     * 解析外部编码，即指标id。这里待解析的是两位两位的ascii码
     * @param indicatorAscii 待解析的指标ascii码
     * @return
     */
    private String parseIndicatorAscii(String indicatorAscii) {
        if (StrUtil.isBlank(indicatorAscii)) {
            throw new CustomerException(ParamCode.PARAM_IS_BLANK, "解析指标id时，对应的信息不存在");
        }
        if (indicatorAscii.length() % 2 != 0) {
            throw new CustomerException(ParamCode.PARAM_IS_INVALID, "解析指标id时，对应的信息不合法");
        }
        int bitCount = indicatorAscii.length() / 2;
        StringBuilder sb = new StringBuilder();
        for (int i = 0 ; i < bitCount ; i++) {
            String ascii = indicatorAscii.substring(i * 2, (i + 1) * 2);
            sb.append(AsciiUtils.getAsciiFromStr(ascii));
        }
        return sb.toString();
    }

    /**
     * 组装并返回采集对象
     * @param indicatorId 指标id
     * @param alarmType 报警种类
     * @return
     */
    private CollectData assembleAndReturn(String indicatorId, String alarmType) {
        List<String> values = new ArrayList<>();
        values.add(alarmType);
        return new CollectData(indicatorId, CollectTypeEnum.FIRE, values);
    }

    /**
     * 解析报警类型
     * @param alarmTypeHex 报警类型对应的十六进制
     * @return
     */
    private String parseAlarmTypeHex(String alarmTypeHex) {
        if (StrUtil.isBlank(alarmTypeHex) || alarmTypeHex.length() != 2) {
            throw new CustomerException(ParamCode.PARAM_IS_INVALID, "解析报警类型时，报警类型不合法");
        }
        Integer alarmType = ConvertUtils.hexToDec(alarmTypeHex);
        return TandaFireControlMainEngineCodeEnum.getMessage(alarmType + "");
    }

    /**
     * 根据ip获取指标id
     * @param ip 信道ip地址
     * @return
     */
    private Gateway getGatewayByIp(String ip) {
        List<Gateway> gatewayVoList = CfgContext.getGatewayVoList();
        if (CollectionUtil.isEmpty(gatewayVoList)) {
            throw new CustomerException(ResultCode.RETURN_DATA_NOT_FOUND, "没有找到这条消息对应的网关");
        }
        List<Gateway> collect = gatewayVoList.stream().filter(item -> item.getGatewayIp().equals(ip)).collect(Collectors.toList());
        if (CollectionUtil.isEmpty(collect)) {
            throw new CustomerException(ResultCode.RETURN_DATA_NOT_FOUND, "没有找到这条消息对应的网关");
        }
        return collect.get(0);
    }
}
